#include "Subsystems/SubsystemProjectiles.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Subsystems/SubsystemBodies.h"
#include "Subsystems/SubsystemPickables.h"
#include "Subsystems/SubsystemExplosions.h"
#include "Subsystems/BlockBehaviors/SubsystemBlockBehaviors.h"
#include "Subsystems/BlockBehaviors/SubsystemFireBlockBehavior.h"

#include "Network/PackageManager.h"
#include "Network/Packages/ProjectilePackage.h"
#include "Blocks/WaterBlock.h"
#include "Blocks/MagmaBlock.h"
#include "Game/BodyRaycastResult.hpp"
#include "Managers/BlocksManager.h"
#include "Managers/GameManager.h"

#include "Components/ComponentOnFire.h"
#include "Components/ComponentAttacker.h"
#include "Components/ComponentBody.h"
#include "Components/ComponentCreature.h"

#include "Log.h"
#include "GameRandom.hpp"
#include "GameSingleton.h"
#include "GameRegistry.hpp"
#include "GameThreadPool.hpp"


namespace PocketSurvival
{

    static void CalculateVelocityAlignMatrix(const Vector3 &position, const Vector3 &velocity, Matrix &outMatrix)
    {
		outMatrix = FastMatrix::Identity;
		outMatrix.setUp(Vector3::Normalize(velocity));
        outMatrix.setRight(Vector3::Normalize(Vector3::Cross(outMatrix.getUp(), FastVector3::UnitY)));
        outMatrix.setForward(Vector3::Normalize( Vector3::Cross(outMatrix.getUp(), outMatrix.getRight()) ));
		outMatrix.setTranslation(position);
    }

    static std::function<bool(int32_t, float)> callFunc = [](int32_t value, float distance) -> bool
    {
        return GameSingleton::blocksManager->blocks[Terrain::ExtractContents(value)]->IsCollidable;
    };

    SubsystemProjectiles::SubsystemProjectiles()
    {
        m_projectileVec.reserve(500);
    }

    Projectile *SubsystemProjectiles::addProjectile(int32_t value, const Vector3 &position, const Vector3 &velocity, const Vector3 &angularVelocity, ComponentCreature* owner)
    {
        Projectile *projectile = addProjectileNet(value, position, velocity, angularVelocity, owner);
        GameSingleton::packageManager->addSendPackageToQueue(std::make_shared<ProjectilePackage>(projectile));
        return projectile;
    }

    Projectile *SubsystemProjectiles::addProjectileNet(int32_t value, const Vector3 &position, const Vector3 &velocity, const Vector3 &angularVelocity, ComponentCreature* owner)
    {
        Projectile *projectile = new Projectile;
        projectile->value = value;
        projectile->angularVelocity = angularVelocity;
        projectile->position = position;
        projectile->velocity = velocity;
        projectile->rotation = FastVector3::Zero;
        projectile->creationTime = m_gameInfo->totalElapsedGameTime;
        projectile->isInWater = isWater(position);
        projectile->ownerEntity = (owner == nullptr ? entt::null : owner->entity);
        projectile->projectileStoppedAction = ProjectileStoppedAction::TurnIntoPickable;
        projectile->isIncendiary = false;

        std::lock_guard<std::mutex> lk(m_projectilesMutex);
        m_projectileVec.push_back(projectile);
        return projectile;
    }

    Projectile *SubsystemProjectiles::fireProjectile(int32_t value, const Vector3 &position, const Vector3 &velocity, const Vector3 &angularVelocity, ComponentCreature* owner)
    {
        Projectile *projectile = fireProjectileNet(value, position, velocity, angularVelocity, owner);
        if(projectile != nullptr)
        {
            projectile->isFireProjectile = true;
            GameSingleton::packageManager->addSendPackageToQueue(std::make_shared<ProjectilePackage>(projectile));
        }
        return projectile;
    }

    Projectile *SubsystemProjectiles::fireProjectileNet(int32_t value, const Vector3 &position, const Vector3 &velocity, const Vector3 &angularVelocity, ComponentCreature* owner)
    {
        int32_t contents = Terrain::ExtractContents(value);
        Block *block = GameSingleton::blocksManager->blocks[contents];
        if(block == nullptr)
        {
            return nullptr;
        }
        Vector3 v = Vector3::Normalize(velocity);
        Vector3 vector = position;
        if(owner != nullptr)
        {
            Ray3 ray(position + v * 5.0f, -v);
            BoundingBox boundingBox = owner->componentBody->getBoundingBox();
            boundingBox.Min -= 0.4f;
            boundingBox.Max += 0.4f;
            float num2 = 0.0f;
            if(ray.Intersection(boundingBox, num2) == true)
            {
                if(num2 == 0.0f)
                {
                    return nullptr;
                }
                vector = position + v * (5.0f - num2 + 0.1f);
            }
        }
        Vector3 end = vector + v * block->ProjectileTipOffset;

        if(m_terrain->raycast(position, end, false, true, callFunc) == nullptr)
        {
            Projectile *projectile = addProjectileNet(value, vector, velocity, angularVelocity, owner);
            std::vector<SubsystemBlockBehavior *> &blockBehaviors = m_blockBehaviors->getBlockBehaviors(Terrain::ExtractContents(contents));
            for(auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
            {
                (*behaviorIt)->onFiredAsProjectile(projectile);
            }
            return projectile;
        }
        return nullptr;
    }

    bool SubsystemProjectiles::isWater(const Vector3 &position)
    {
        int32_t cellContents = m_terrain->getCellContents(Terrain::ToCell(position.posX), Terrain::ToCell(position.posY), Terrain::ToCell(position.posZ));
        return GameSingleton::blocksManager->blocks[cellContents]->isBlock<WaterBlock*>();
    }
    bool SubsystemProjectiles::isMagma(const Vector3 &position)
    {
        int32_t cellContents = m_terrain->getCellContents(Terrain::ToCell(position.posX), Terrain::ToCell(position.posY), Terrain::ToCell(position.posZ));
        return GameSingleton::blocksManager->blocks[cellContents]->isBlock<MagmaBlock*>();
    }

    void SubsystemProjectiles::update(float dt)
    {
        double totalElapsedGameTime = m_gameInfo->totalElapsedGameTime;

        std::lock_guard<std::mutex> lk(m_projectilesMutex);

        bool hasRemove = false;
        for(auto it = m_projectileVec.begin(); it != m_projectileVec.end(); ++it)
        {
            if((*it)->toRemove == true)
            {
                hasRemove = true;
            }
            else
            {
                Block *block = GameSingleton::blocksManager->blocks[Terrain::ExtractContents((*it)->value)];

                Vector3 &position = (*it)->position;
                Vector3 &velocity = (*it)->velocity;
                if(totalElapsedGameTime - (*it)->creationTime > 40.0f || position.posY < 0.0f)
                {
                    (*it)->toRemove = true;
                }

                TerrainChunk *chunkAtCell = m_terrain->getTerrainChunk(Terrain::ToCell(position.posX) >> 4, Terrain::ToCell(position.posZ) >> 4);
                if(chunkAtCell == nullptr || chunkAtCell->State <= TerrainChunkState::InvalidContents4)
                {
                    (*it)->noChunk = true;
                }
                else
                {
                    (*it)->noChunk = false;
                    Vector3 vector = position + velocity * dt;
                    Vector3 v = block->ProjectileTipOffset * Vector3::Normalize(velocity);
                    std::shared_ptr<BodyRaycastResult> bodyRaycastResult = m_bodies->raycast(position + v, vector + v, 0.2f, [](ComponentBody *, float) -> bool { return true;});
                    Vector3 positionEnd = vector + v;
                    std::shared_ptr<TerrainRaycastResult> terrainRaycastResult = m_terrain->raycast(position + v, positionEnd, false, true, callFunc);
                    bool flag = block->DisintegratesOnHit;
                    if(terrainRaycastResult != nullptr || bodyRaycastResult != nullptr)
                    {
                        std::shared_ptr<CellFace> cellFace = (terrainRaycastResult != nullptr ? std::make_shared<CellFace>(terrainRaycastResult->cellFace) : nullptr);
                        ComponentBody *componentBody = (bodyRaycastResult != nullptr ? bodyRaycastResult->componentBody : nullptr);
                        std::vector<SubsystemBlockBehavior *> &blockBehaviors = m_blockBehaviors->getBlockBehaviors(Terrain::ExtractContents((*it)->value));
                        for(auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
                        {
                            flag |= (*behaviorIt)->onHitAsProjectile(cellFace, componentBody, (*it));
                        }
                        (*it)->toRemove |= flag;
                    }
                    std::shared_ptr<Vector3> vector2Ptr= nullptr;
                    if(bodyRaycastResult != nullptr && (terrainRaycastResult == nullptr || bodyRaycastResult->distance < terrainRaycastResult->distance))
                    {
                        if(velocity.length() > 10.0f)
                        {
                            ComponentAttacker::AttackBody(bodyRaycastResult->componentBody, (*it)->ownerEntity, bodyRaycastResult->hitPoint(), Vector3::Normalize(velocity), block->getProjectilePower((*it)->value), false);
                        }
                        if((*it)->isIncendiary == true)
                        {
                            ComponentOnFire **componentOnFirePtr = GameSingleton::gameRegistry->try_get<ComponentOnFire*>(bodyRaycastResult->componentBody->entity);
                            if(componentOnFirePtr != nullptr)
                            {
                                (*componentOnFirePtr)->setOnFile((*it)->ownerEntity, GameSingleton::gameRandom->getFloat(6.0f, 8.0f));
                            }
                        }
                        vector = position;
                        velocity *= -0.05f;
                        velocity += GameSingleton::gameRandom->getVector3(0.33f * velocity.length());
                        (*it)->angularVelocity += -0.05f;
                    }
                    else if(terrainRaycastResult != nullptr)
                    {
                        const CellFace &cellFace2 = terrainRaycastResult->cellFace;
                        int32_t cellContents = m_terrain->getCellContents(cellFace2.point);
                        Block *block2 = GameSingleton::blocksManager->blocks[cellContents];
                        // 这里或许需要做空指针判断 未完成...


                        float num2 = velocity.length();
                        std::vector<SubsystemBlockBehavior *> &blockBehaviors = m_blockBehaviors->getBlockBehaviors(Terrain::ExtractContents(cellContents));
                        for(auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
                        {
                            (*behaviorIt)->onHitByProjectile(cellFace2, *it);
                        }
                        if(num2 > 10.0f && GameSingleton::gameRandom->getFloat(0.0f, 1.0f) > block2->ProjectileResilience)
                        {
                            m_terrain->destroyCell(0, cellFace2.point.posX, cellFace2.point.posY, cellFace2.point.posZ, 0, true);
                        }
                        // 附带火焰，并且击中可以燃烧的方块会使其点燃
                        if((*it)->isIncendiary)
                        {
                            // 使方块着火

                            m_fireBlockBehavior->setCellOnFire(terrainRaycastResult->cellFace.point, 1.0f);
                            Vector3 vector3 = (*it)->position - 0.75f * Vector3::Normalize((*it)->velocity);
                            for(int32_t k = 0; k < 8; k++)
                            {
                                Vector3 v2 = (k == 0 ? Vector3::Normalize((*it)->velocity) : GameSingleton::gameRandom->getVector3(1.5f));
                                Vector3 endPosition = vector3 + v2;
                                auto terrainRaycastResult2 = m_terrain->raycast(vector3, endPosition, false, true, [](int, float) -> bool { return true; });
                                if(terrainRaycastResult2 != nullptr)
                                {
                                    m_fireBlockBehavior->setCellOnFire(terrainRaycastResult2->cellFace.point, 1.0f);
                                }
                            }
                        }
                        if(block->IsStickable && num2 > 10.0f && GameSingleton::gameRandom->getBool(block2->ProjectileStickProbability))
                        {
                            Vector3 v3 = Vector3::Normalize(velocity);
                            float s = MathUtils::Lerp(0.1f, 0.2f, MathUtils::Saturate((num2 - 15.0f) / 20.0f));
                            vector2Ptr = std::make_shared<Vector3>(position + terrainRaycastResult->distance * Vector3::Normalize(velocity) + v3 * s);
                        }
                        else
                        {
                            Plane plane = cellFace2.calculatePlane();
                            vector = position;
                            if(plane.Normal.posX != 0.0f)
                            {
                                velocity *= Vector3(-0.3f, 0.3f, 0.3f);
                            }
                            if(plane.Normal.posY != 0.0f)
                            {
                                velocity *= Vector3(0.3f, -0.3f, 0.3f);
                            }
                            if(plane.Normal.posZ != 0.0f)
                            {
                                velocity *= Vector3(0.3f, 0.3f, -0.3f);
                            }
                            float num3 = velocity.length();
                            velocity = num3 * Vector3::Normalize(velocity + GameSingleton::gameRandom->getVector3(num3 / 6.0f, num3 / 3.0f));
                            (*it)->angularVelocity *= -0.3f;
                        }
                    }
                    if( ( terrainRaycastResult != nullptr || bodyRaycastResult != nullptr ) && flag == false )
                    {
                        if((*it)->toRemove == false && ( vector2Ptr != nullptr || velocity.length() < 1.0f) )
                        {
                            if((*it)->projectileStoppedAction == ProjectileStoppedAction::TurnIntoPickable)
                            {
                                int32_t num4 = GameSingleton::blocksManager->damageItem((*it)->value, 1);
                                if(num4 != 0)
                                {
                                    if(vector2Ptr != nullptr)
                                    {
                                        std::shared_ptr<Matrix> matrix = std::make_shared<Matrix>();
                                        CalculateVelocityAlignMatrix(*vector2Ptr, velocity, *matrix);
                                        m_pickables->addPickable(num4, 1, position, std::make_shared<Vector3>(0.0f, 0.0f, 0.0f), matrix);
                                    }
                                    else
                                    {
                                        m_pickables->addPickable(num4, 1, position, std::make_shared<Vector3>(0.0f, 0.0f, 0.0f), nullptr);
                                    }
                                }
                                (*it)->toRemove = true;
                            }
                            else if((*it)->projectileStoppedAction == ProjectileStoppedAction::Disappear)
                            {
                                (*it)->toRemove = true;
                            }
                        }
                    }
                    float num5 = (*it)->isInWater ? MathUtils::Pow(0.001f, dt) : MathUtils::Pow(block->ProjectileDamping, dt);
                    velocity.posY += -9.8f * dt;
                    velocity *= num5;
                    (*it)->angularVelocity *= num5;
                    position = vector;
                    (*it)->rotation += (*it)->angularVelocity * dt;
                    
                    // 撞击到水流
                    bool flag2 = isWater(position);
                    if((*it)->isInWater != flag2)
                    {
                        if(flag2)
                        {
                            float num6 = Vector2(velocity.posX, velocity.posZ).length();
                            if(num6 > 6.0f && num6 > 4.0f * std::abs(velocity.posY))
                            {
                                velocity *= 0.5f;
                                velocity.posY *= -1.0f;
                                flag2 = false;
                            }
                            else
                            {
                                velocity *= 0.2f;
                            }
                        }
                        (*it)->isInWater = flag2;
                    }
                    // 撞击到岩浆
                    if(isMagma(position))
                    {
                        (*it)->toRemove = true;
                        Point3 point = Terrain::ToCell((*it)->position);
                        m_explosions->tryExplodeBlock(point.posX, point.posY, point.posZ, (*it)->value);
                    }
                    // 爆炸物... 未完成...

                }
            }
        }

        if(hasRemove == true)
        {
            m_newProjectileVec.clear();
            m_newProjectileVec.reserve(m_projectileVec.size() + 100);

            for(Projectile *projectile : m_projectileVec)
            {
                if(true == projectile->toRemove)
                {
                    delete projectile;
                    continue;
                }
                m_newProjectileVec.push_back(projectile);
            }

            m_projectileVec.swap(m_newProjectileVec);
        }
    }



    void SubsystemProjectiles::internalSave()
    {
        nlohmann::json projectileArrayJson = nlohmann::json::array();

        m_projectilesMutex.lock();
        for (Projectile *projectile : m_projectileVec)
        {
            nlohmann::json projectileJson = {
                {"Value",projectile->value}, 
                {"Position", projectile->position}, 
                {"Velocity", projectile->velocity}, 
                {"CreationTime",projectile->creationTime}
            };
            projectileArrayJson.push_back(projectileJson);
        }
        m_projectilesMutex.unlock();

        std::string projectileArrayFile = fmt::format("{}/Projectiles.json", GameSingleton::gameManager->getNowWorldPath());
        if(Utils::SaveTextFile(projectileArrayFile, projectileArrayJson.dump()) == false)
        {
            Log::Error(fmt::format("无法保存Projectile数据到文件: {}", projectileArrayFile));
            // throw std::runtime_error(fmt::format("无法保存Projectile数据到文件: {}", projectileArrayFile)); 
        }
    }


    void SubsystemProjectiles::load(const nlohmann::json &json)
    {
        m_pickables = GameSingleton::singletonPtr->getSingleton<SubsystemPickables>();
        m_bodies = GameSingleton::singletonPtr->getSingleton<SubsystemBodies>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
        m_blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>();
        m_fireBlockBehavior = GameSingleton::singletonPtr->getSingleton<SubsystemFireBlockBehavior>();
        m_explosions = GameSingleton::singletonPtr->getSingleton<SubsystemExplosions>();

        std::lock_guard<std::mutex> lk(m_projectilesMutex);

        std::string jsonText;
        if(Utils::LoadTextFile(fmt::format("{}/Projectiles.json", GameSingleton::gameManager->getNowWorldPath()), jsonText) == true)
        {
            nlohmann::json projectileArrayJson = nlohmann::json::parse(jsonText);
            for(const nlohmann::json &pickableJson : projectileArrayJson)
            {
                Projectile *projectile = new Projectile;
                projectile->value = pickableJson["Value"].get<int32_t>();
                projectile->position = pickableJson["Position"].get<Vector3>();
                projectile->velocity = pickableJson["Velocity"].get<Vector3>();
                projectile->creationTime = pickableJson["CreationTime"].get<double>();
                m_projectileVec.push_back(projectile);
            }
        }
    }

    void SubsystemProjectiles::save(nlohmann::json &json)
    {
        // 交由线程池进行保存处理
        GameSingleton::gameSaveThreadPool->push_task(std::bind(&SubsystemProjectiles::internalSave, this));
    }

    void SubsystemProjectiles::saveToMsgJson(nlohmann::json &jsonData)
    {
        jsonData["Projectiles"] = nlohmann::json::object();
    }


    static const std::string subsystemName = "Projectiles";
    const std::string& SubsystemProjectiles::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemProjectiles::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}